/*******************************************************************************
 * Copyright (c) 2009 - 2012, 2018 TU Vienna/ACIN, 2017 fortiss GmbH
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Martin Melik Merkumians, Alois Zoitl, Ingo Hegny,
 *      - initial implementation and rework communication infrastructure
 *    Stefan Profanter
 *      - list->begin changed to be const, and smaller bugfixes to support
 *        const as Type
 *     Martin Melik Merkumians - fixes naming, adds missing back functions for
 *       list specializations
 *******************************************************************************/
#include "fortenode.h"
#include "forteiterator.h"

#ifndef _FORTELIST_H_
#define _FORTELIST_H_

/*! \ingroup CORE\brief FORTE implementation of a Singly Linked List
 */

template<typename T, typename Container = CSinglyLinkedListNode<T>>
class [[deprecated("Use STL containers instead. Preferably std::vector")]] CSinglyLinkedList {
  private:
    friend class CIterator<T, Container>;

    /*!\brief First element of the singly linked list
     */
    Container *mFirstNode;

    /*!\brief Last element of the singly linked list
     */
    Container *mLastNode;

  public:
    CSinglyLinkedList();
    ~CSinglyLinkedList();

    /*!\brief Typedef for getting the right iterator for the list
     * */
    typedef CIterator<T, Container> Iterator;

    /*!\brief Add's the given object at the start of the singly linked list
     *
     * \param paElement the reference to the object to be added
     */
    void pushFront(T const &paElement);

    /*!\brief Add's the given object at the end of the singly linked list
     *
     * \param paElement the reference to the object to be added
     */
    void pushBack(T const &paElement);

    /*!\brief Deletes the first object of the singly linked list
     *
     */
    void popFront();

    /*!\brief Deletes all objects in the singly linked list
     */
    void clearAll();

    /*!\brief checks if the singly linked list is empty or not
     *
     * \return true if the list is empty, false if not
     */
    bool isEmpty() const {
      return (nullptr == mFirstNode);
    }

    /*!\brief returns an iterator with it's current position set to the start of the singly linked list
     *
     * \return iterator on position FirstNode
     */
    const Iterator begin() const {
      return Iterator(mFirstNode);
    }

    /*!\brief returns an iterator with it's current position set to 0
     *
     * \return iterator with current position 0
     */
    const Iterator end() const {
      return Iterator(nullptr);
    }

    const Iterator back() const {
      return Iterator(mLastNode);
    }

    /*!\brief erases the next node element after the position of the iterator
     *
     * \param it  iterator set on the element before the element to delete
     * \return Iterator set on the new next node element
     */
    const Iterator eraseAfter(Iterator &it);

    /*! \brief erases the element according to the passed pointer
     *
     * @param paToDelete pointer to the element to be deleted
     */
    void erase(T const &paToDelete);
};

template<typename T, typename Container>
inline CSinglyLinkedList<T, Container>::CSinglyLinkedList() : mFirstNode(nullptr), mLastNode(nullptr) {
}

template<typename T, typename Container>
inline CSinglyLinkedList<T, Container>::~CSinglyLinkedList() {
  clearAll();
}

template<typename T, typename Container>
void CSinglyLinkedList<T, Container>::pushFront(T const &paElement) {
  Container *poNewNode = new Container(paElement, mFirstNode);
  mFirstNode = poNewNode;
  if (nullptr == mLastNode) {
    mLastNode = poNewNode;
  }
}

template<typename T, typename Container>
void CSinglyLinkedList<T, Container>::pushBack(T const &paElement) {
  Container *poNewNode = new Container(paElement);

  if (nullptr != mLastNode) {
    mLastNode->setNext(poNewNode);
  } else {
    mFirstNode = poNewNode;
  }
  mLastNode = poNewNode;
}

template<typename T, typename Container>
void CSinglyLinkedList<T, Container>::popFront() {
  Container *pNodeToDelete = mFirstNode;
  mFirstNode = mFirstNode->getNext();
  if (nullptr == mFirstNode) {
    mLastNode = nullptr;
  }
  delete pNodeToDelete;
}

template<typename T, typename Container>
inline void CSinglyLinkedList<T, Container>::clearAll() {
  while (mFirstNode != nullptr) {
    popFront();
  }
}

template<typename T, typename Container>
const CIterator<T, Container> CSinglyLinkedList<T, Container>::eraseAfter(Iterator &it) {
  Container *pNodeToDelete = (it.getPosition())->getNext();
  it.getPosition()->setNext(pNodeToDelete->getNext());
  if (nullptr == it.getPosition()->getNext()) {
    mLastNode = it.getPosition();
  }
  delete pNodeToDelete;
  return Iterator((it.getPosition())->getNext());
}

template<typename T, typename Container>
void CSinglyLinkedList<T, Container>::erase(T const &paToDelete) {

  Iterator itRunner = begin();
  Iterator itRefNode = end();
  Iterator itEnd = end();

  while (itRunner != itEnd) {
    if (*itRunner == paToDelete) {
      if (itRefNode == itEnd) {
        popFront();
      } else {
        eraseAfter(itRefNode);
      }
      break;
    }

    itRefNode = itRunner;
    ++itRunner;
  }
}

/*!\brief explicit specialization for a void* list
 *
 */

template<>
class CSinglyLinkedList<void *, CSinglyLinkedListNode<void *>> {
  private:
    friend class CIterator<void *, CSinglyLinkedListNode<void *>>;

    /*!\brief First element of the singly linked list
     */
    CSinglyLinkedListNode<void *> *mFirstNode;

    /*!\brief Last element of the singly linked list
     */
    CSinglyLinkedListNode<void *> *mLastNode;

  public:
    CSinglyLinkedList() : mFirstNode(nullptr), mLastNode(nullptr) {
    }
    ~CSinglyLinkedList() {
      clearAll();
    }

    /*!\brief Typedef for getting the right iterator for the list
     * */
    typedef CIterator<void *, CSinglyLinkedListNode<void *>> Iterator;

    /*!\brief Add's the given object at the start of the singly linked list
     *
     * \param paElement the reference to the object to be added
     */
    void pushFront(const void *paElement) {
      CSinglyLinkedListNode<void *> *poNewNode = new CSinglyLinkedListNode<void *>((void *) paElement, mFirstNode);
      mFirstNode = poNewNode;
      if (nullptr == mLastNode) {
        mLastNode = poNewNode;
      }
    }

    /*!\brief Add's the given object at the end of the singly linked list
     *
     * \param paElement the reference to the object to be added
     */
    void pushBack(void *const paElement) {
      CSinglyLinkedListNode<void *> *poNewNode = new CSinglyLinkedListNode<void *>(paElement);

      if (nullptr != mLastNode) {
        mLastNode->setNext(poNewNode);
      } else {
        mFirstNode = poNewNode;
      }
      mLastNode = poNewNode;
    }

    /*!\brief Returns the element at first position without deleting it
     *
     */
    inline const void *peekFront() const {
      return (mFirstNode != nullptr) ? mFirstNode->getData() : 0;
    }

    /*!\brief Deletes the first object of the singly linked list
     *
     */
    inline void popFront() {
      CSinglyLinkedListNode<void *> *pNodeToDelete = mFirstNode;
      mFirstNode = mFirstNode->getNext();
      if (nullptr == mFirstNode) {
        mLastNode = nullptr;
      }
      delete pNodeToDelete;
    }

    /*!\brief Deletes all objects in the singly linked list
     */
    inline void clearAll() {
      while (nullptr != mFirstNode) {
        popFront();
      }
    }

    /*!\brief checks if the singly linked list is empty or not
     *
     * \return true if the list is empty, false if not
     */
    inline bool isEmpty() const {
      return (nullptr == mFirstNode);
    }

    /*!\brief returns an iterator with it's current position set to the start of the singly linked list
     *
     * \return iterator on position FirstNode
     */
    inline const Iterator begin() const {
      return Iterator(mFirstNode);
    }

    /*!\brief returns an iterator with it's current position set to 0
     *
     * \return iterator with current position 0
     */
    inline const Iterator end() const {
      return Iterator(nullptr);
    }

    inline const Iterator back() const {
      return Iterator(mLastNode);
    }

    /*!\brief erases the next node element after the position of the iterator
     *
     * \param it  an iterator set on the element before the element to delete
     * \return Iterator set on the new next node element
     */
    const Iterator eraseAfter(Iterator &it) {
      CSinglyLinkedListNode<void *> *pNodeToDelete = (it.getPosition())->getNext();
      it.getPosition()->setNext(pNodeToDelete->getNext());
      if (nullptr == it.getPosition()->getNext()) {
        mLastNode = it.getPosition();
      }
      delete pNodeToDelete;
      return Iterator((it.getPosition())->getNext());
    }

    /*! \brief erases the element according to the passed pointer
     *
     * @param paToDelete pointer to the element to be deleted
     */
    void erase(void *paToDelete) {

      Iterator itRunner = begin();
      Iterator itRefNode = end();
      Iterator itEnd = end();

      while (itRunner != itEnd) {
        if (*itRunner == paToDelete) {
          if (itRefNode == itEnd) {
            popFront();
          } else {
            eraseAfter(itRefNode);
          }
          break;
        }

        itRefNode = itRunner;
        ++itRunner;
      }
    }
};

/*!\brief partial specialization for pointer lists
 */

template<typename T>
class CSinglyLinkedList<T *> {
  private:
    friend class CIterator<T *, CSinglyLinkedListNode<void *>>;
    CSinglyLinkedList<void *> mList;

  public:
    CSinglyLinkedList() = default;

    ~CSinglyLinkedList() {
      clearAll();
    }

    /*!\brief Typedef for getting the right iterator for the list
     * */
    typedef CIterator<T *, CSinglyLinkedListNode<void *>> Iterator;

    /*!\brief Add's the given object at the start of the singly linked list
     *
     * \param paElement the reference to the object to be added
     */
    inline void pushFront(T *const paElement) {
      mList.pushFront(paElement);
    }

    /*!\brief Add's the given object at the end of the singly linked list
     *
     * \param paElement the reference to the object to be added
     */
    inline void pushBack(T *const paElement) {
      mList.pushBack(paElement);
    }

    /*!\brief Returns the element at first position without deleting it
     *
     */
    inline const T *peekFront() const {
      return (const T *) mList.peekFront();
    }

    /*!\brief Deletes the first object of the singly linked list
     *
     */
    inline void popFront() {
      mList.popFront();
    }

    /*!\brief Deletes all objects in the singly linked list
     */
    inline void clearAll() {
      mList.clearAll();
    }

    /*!\brief checks if the singly linked list is empty or not
     *
     * \return true if the list is empty, false if not
     */
    inline bool isEmpty() const {
      return mList.isEmpty();
    }

    /*!\brief returns an iterator with it's current position set to the start of the singly linked list
     *
     * \return iterator on position FirstNode
     */
    inline const Iterator begin() const {
      return Iterator((mList.begin()).getPosition());
    }

    /*!\brief returns an iterator with it's current position set to 0
     *
     * \return iterator with current position 0
     */
    inline const Iterator end() const {
      return Iterator((mList.end()).getPosition());
    }

    inline const Iterator back() const {
      return Iterator(mList.back().getPosition());
    }

    /*!\brief erases the next node element after the position of the iterator
     *
     * \param it  an iterator set on the element before the element to delete
     * \return Iterator set on the new next node element
     */
    const Iterator eraseAfter(Iterator &it) {
      CSinglyLinkedList<void *>::Iterator itBuff(it.getPosition());
      return Iterator((mList.eraseAfter(itBuff)).getPosition());
    }

    void erase(T *paToDelete) {
      mList.erase(paToDelete);
    }
};

#endif /* CSINGLYLINKEDLIST_H_ */
